/*
@Time : 2018/11/11 下午6:35 
@Author : baoye
@File : transaction
@Software: GoLand
@brief: 
*/

package main

import (
	"bytes"
	"encoding/gob"
	"crypto/sha256"
	"fmt"
	"os"
)

const reward = 12.5

// 交易输入，指明交易发起人可支付资金的来源，一个交易输入对应之前的一个交易输出
type TXInput struct {
	// 引用output所在的交易id
	TXID []byte

	// 引用output所在交易的索引值
	VoutIndex int64

	// 解锁脚本
	ScriptSig string
}

type TXOutput struct {
	// 接收的金额
	Value float64

	// 锁定脚本
	ScriptPubKey string
}

type Transaction struct {
	// 交易ID
	TXID []byte

	// 交易的输入
	TXInputs []TXInput

	// 交易的输出
	TXOutputs []TXOutput
}


// 设置交易ID的方法
func (tx *Transaction)setHash()  {
	var buffer bytes.Buffer

	encoder := gob.NewEncoder(&buffer)
	encoder.Encode(tx)

	hash := sha256.Sum256(buffer.Bytes())

	tx.TXID = hash[:]
}


// 创建coinbase交易
// coinbase总是新区快的第一条交易，这条交易中只有一个输出，即对矿工的奖励，没有输入
// address是矿工的地址，data是矿工的自定义附加消息
func NewCoinbaseTX(address string, data string) *Transaction  {

	input := TXInput{nil,-1,data}
	output := TXOutput{reward, address}

	txTmp := Transaction{nil, []TXInput{input}, []TXOutput{output}}
	txTmp.setHash()

	return &txTmp
}


func (input *TXInput)CanUnlockUTXOWith(unlockData string) bool {
	return input.ScriptSig == unlockData
}

func (output *TXOutput) CanBeUnlockedWith(unlockData string) bool  {
	return output.ScriptPubKey == unlockData
}

//返回指定地址能够支配的utxo所在的交易的集合
func (bc *BlockChain) FindUTXOTransactions(address string) []Transaction {

	var transactions []Transaction

	spentUTXOs := make(map[string][]int64)
	it := NewBlockchainIter(*bc)

	for {
		//遍历区块
		block := it.GetBlockAndMoveLeft()

		//遍历交易
		for _, tx := range block.Transactions {

		OUTPUTS:
		//遍历outputs
			for currentIndex, output := range tx.TXOutputs {
				//过滤已经消耗过得utxo
				if spentUTXOs[string(tx.TXID)] != nil {
					//交易id非空， 意味着在交易里面有消耗的utxo
					indexs := spentUTXOs[string(tx.TXID)]

					for _, index := range indexs {
						if int64(currentIndex) == index {
							continue OUTPUTS
						}
					}
				}

				if output.CanBeUnlockedWith(address) {
					transactions = append(transactions, *tx)
				}
			}

			//遍历inputs
			if !tx.IsCoinbase() {
				for _, input := range tx.TXInputs {
					//检查一下当前的inputs是否和地址有关，能否解开
					//看看当前花费的这个input是不是和指定的地址有关，如果有关，要记录下来，
					//这样遍历前面的交易时，就不会再对这个引用的output重复统计了。
					if input.CanUnlockUTXOWith(address) {
						spentUTXOs[string(input.TXID)] = append(spentUTXOs[string(input.TXID)], input.VoutIndex)
						//map[0x000000] = []int64{0}
						//map[0x111111] = []int64{2,5}
						//map[0xnnnnnn] = []int64{3}
					}
				}
			}
		}
		if len(block.PrevBlockHash) == 0 {
			break
		}
	}
	return transactions
}


func (tx *Transaction) IsCoinbase() bool {
	if len(tx.TXInputs) == 1 {
		if tx.TXInputs[0].TXID == nil && tx.TXInputs[0].VoutIndex == -1 {
			return true
		}
	}
	return false
}


//返回指定地址能够支配的utxo的集合
func (bc *BlockChain) FindUTXOs(address string) []TXOutput {
	txs := bc.FindUTXOTransactions(address)

	var utxos []TXOutput
	for _, tx := range txs {
		for _, output := range tx.TXOutputs {
			//找到自己能解锁的output
			if output.CanBeUnlockedWith(address) {
				utxos = append(utxos, output)
			}
		}
	}

	return utxos
}

// 新建普通交易
func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction  {
	// 有效UTXO集合
	validUTXOs := make(map[string][]int64)
	var total float64

	// 找到所需要的UTXO的集合
	validUTXOs, total = bc.FindSuitableUTXOs(from, amount)

	if total < amount {
		fmt.Println("money is not enough!!")
		os.Exit(1)
	}


	// 将返回的utxo转换成input
	var inputs []TXInput

	for txid, indexs := range validUTXOs {
		for _, index := range indexs {
			input := TXInput{[]byte(txid), index, from}
			inputs = append(inputs,input)
		}
	}
	// 创建output
	var outputs []TXOutput
	output := TXOutput{amount, to}
	outputs = append(outputs, output)

	// 设置找零
	if total > amount {
		outputs = append(outputs, TXOutput{total - amount, from})
	}

	txTmp := Transaction{nil, inputs, outputs}
	txTmp.setHash()
	return &txTmp
}


func (bc *BlockChain) FindSuitableUTXOs(address string, amount float64) (map[string][]int64, float64) {
	fmt.Printf("FindNeedUTXOs\n")

	validUTXOs := make(map[string][]int64)
	calc := 0.0 //10 + 2 + 3

	txs := bc.FindUTXOTransactions(address)
	for _, tx := range txs {
		for index, output := range tx.TXOutputs {
			if output.CanBeUnlockedWith(address) {
				calc += output.Value
				validUTXOs[string(tx.TXID)] = append(validUTXOs[string(tx.TXID)], int64(index))
				if calc >= amount {
					break
				}
			}
		}
	}

	return validUTXOs, calc
}






